package net.jalbum.filters;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;

public class ResizableBox extends ResizableObject{

	/**
	 * 
	 */
	private static final long serialVersionUID = 7200291618612187396L;
	protected boolean keepDimension = false;
	protected boolean keepRatio=false;
	protected boolean keepRatioKey=false;
	protected boolean keepSquare = false;
	protected double ratio;

	public ResizableBox() {
		super();
	}

	public ResizableBox(int width, int height) {
		super(width,height);
	}
	
	public ResizableBox(int width, int height, ResizableObjectPanel parentPanel){
		this(width,height);
		setParentPanel(parentPanel);
	}

	protected void paintHandles(Graphics g) {
		//check mouse positions
		if(position==MOVE||position==RESIZE_NW||position==RESIZE_NE||position==RESIZE_SE||position==RESIZE_SW||position==RESIZE_N||position==RESIZE_E||position==RESIZE_S||position==RESIZE_W){
			if(!keepDimension){
				super.paintHandles(g);
			}

			//calculate the positions as pixel size
			int intX1 = (int)(OutX1*(width-1));
			int intX2 = (int)(OutX2*(width-1));
			int intY1 = (int)(OutY1*(height-1));
			int intY2 = (int)(OutY2*(height-1));
			
			if(!keepDimension && !keepRatio && !keepRatioKey){
				if(Math.abs(intX2-intX1)>handlesize*3){
					//paint the white background for the side handles on the top and bottom, if there is enough space
					g.setColor(Color.WHITE);
					g.fillRect(intX1-handlesize/2+(intX2-intX1)/2, intY1-handlesize/2, handlesize, handlesize);
					g.fillRect(intX1-handlesize/2+(intX2-intX1)/2, intY2-handlesize/2, handlesize, handlesize);
		
					//paint the black frame for the side handles on the top and bottom, if there is enough space
					g.setColor(Color.BLACK);
					g.drawRect(intX1-handlesize/2+(intX2-intX1)/2, intY1-handlesize/2, handlesize, handlesize);
					g.drawRect(intX1-handlesize/2+(intX2-intX1)/2, intY2-handlesize/2, handlesize, handlesize);
				}
				
				if(Math.abs(intX2-intX1)>handlesize*3){
					//paint the white background for the side handles on the left and right, if there is enough space
					g.setColor(Color.WHITE);
					g.fillRect(intX1-handlesize/2, intY1-handlesize/2+(intY2-intY1)/2, handlesize, handlesize);
					g.fillRect(intX2-handlesize/2, intY1-handlesize/2+(intY2-intY1)/2, handlesize, handlesize);
		
					//paint the black frame for the side handles on the left and right, if there is enough space
					g.setColor(Color.BLACK);
					g.drawRect(intX1-handlesize/2, intY1-handlesize/2+(intY2-intY1)/2, handlesize, handlesize);
					g.drawRect(intX2-handlesize/2, intY1-handlesize/2+(intY2-intY1)/2, handlesize, handlesize);
				}
			}
		}
	}

	protected int checkState(int x, int y) {
		//check close to which corner or side the mouse is and chance the state accordingly
		int superState = super.checkState(x, y);
		if((superState == RESIZE_NW || superState == RESIZE_NE || superState == RESIZE_SE || superState == RESIZE_SW) && keepDimension){
			return MOVE;
		}else if((superState == RESIZE_N || superState == RESIZE_E || superState == RESIZE_S || superState == RESIZE_W) && (keepDimension || keepRatio || keepRatioKey)){
			return MOVE;
		}else if(superState == MAKE && (keepDimension || keepRatio || keepRatioKey)){
			return NOTHING;
		}else{
			return superState;
		}
	}

    @Override
	protected void changeMousePosition(MouseEvent arg0) {
//        System.out.println("Modifiers: " + arg0.getModifiers());
//        System.out.println("ModifiersEx: " + arg0.getModifiersEx());
//        System.out.println("ModifiersExText: " + arg0.getModifiersExText(arg0.getModifiersEx()));

        keepRatioKey = (arg0.getModifiersEx() & (KeyEvent.SHIFT_DOWN_MASK)) != 0;
//            System.out.println("keepRatioKey: " + keepRatioKey);
		//a method that is called by dragMouse and releaseMouse to draw a current image of the box if it is moved or resized
		int inputX = arg0.getX();
		int inputY = arg0.getY();
		
		if(state == MOVE){
			//checking the boundaries for x and y individually to ensure smooth moving 
			if((!(X1-OffsetX+inputX/(double)(width-1)<0 || X2-OffsetX+inputX/(double)(width-1)>1) && (keepDimension || keepRatio || keepRatioKey)) || (!keepDimension && !keepRatio && !keepRatioKey)){
				//if all X-coordinates remain within the image while one of the "keep-Options" is enabled or if non of the "keep-Options" is enabled, the coordinates are just set to the new value. if they overlap the image borders, they are corrected later by checkDimensions
				X1 = X1-OffsetX+inputX/(double)(width-1);
				X2 = X2-OffsetX+inputX/(double)(width-1);
				OffsetX=inputX/(double)(width-1);
			}else{
				//if one of the Lock options is enabled and the coordinates move out of the image area, the box stops as soon as it hits one of the walls
				double correctionX = 0;
				if (X1-OffsetX+inputX/(double)(width-1)<0){
					correctionX = X1;
					X1=0;
					X2=X2 - correctionX;
				}else if(X2-OffsetX+inputX/(double)(width-1)>1){
					correctionX = 1 - X2;
					X2=1;
					X1=X1 + correctionX;
				}
			}
			//if all Y-coordinates remain within the image while one of the "keep-Options" is enabled or if non of the "keep-Options" is enabled, the coordinates are just set to the new value. if they overlap the image borders, they are corrected later by checkDimensions
			//see the corresponding part for X coordinates for further comments
			if((!(Y1-OffsetY+inputY/(double)(height-1)<0 || Y2-OffsetY+inputY/(double)(height-1)>1) && (keepDimension || keepRatio || keepRatioKey)) || (!keepDimension && !keepRatio && !keepRatioKey)){
				Y1 = Y1-OffsetY+inputY/(double)(height-1);
				Y2 = Y2-OffsetY+inputY/(double)(height-1);
				OffsetY=inputY/(double)(height-1);
			}else{
				double correctionY = 0;
				if (Y1-OffsetY+inputY/(double)(height-1)<0){
					correctionY = Y1;
					Y1=0;
					Y2=Y2 - correctionY;
				}else if(Y2-OffsetY+inputY/(double)(height-1)>1){
					correctionY = 1 - Y2;
					Y2=1;
					Y1=Y1 + correctionY;
				}
			}
		}else if(state == MAKE){
			double[] result = calculateResize(X1,Y1,inputX, inputY);
			X2=result[0];
			Y2=result[1];
		}else if(state==RESIZE_NW){
			double[] result = calculateResize(X2,Y2,inputX, inputY);
			X1=result[0];
			Y1=result[1];
		}else if(state==RESIZE_NE){
			double[] result = calculateResize(X1,Y2,inputX, inputY);
			X2=result[0];
			Y1=result[1];
		}else if(state==RESIZE_SE){
			double[] result = calculateResize(X1,Y1,inputX, inputY);
			X2=result[0];
			Y2=result[1];
		}else if(state==RESIZE_SW){
			double[] result = calculateResize(X2,Y1,inputX, inputY);
			X1=result[0];
			Y2=result[1];
		}else if(state==RESIZE_N){
			Y1 = inputY/(double)(height-1);
		}else if(state==RESIZE_E){
			X2 = inputX/(double)(width-1);
		}else if(state==RESIZE_S){
			Y2 = inputY/(double)(height-1);
		}else if(state==RESIZE_W){
			X1 = inputX/(double)(width-1);
		}
	}

	private double[] calculateResize(double fixedX, double fixedY, int inputX, int inputY) {
		double tempX;
		double tempY;
		if(keepRatio || keepRatioKey){
			//if the height is going to define the size of final box and the width is calculated accordingly
			if(ratio < Math.abs(fixedX-inputX/(double)(width-1))/Math.abs(fixedY-inputY/(double)(height-1))){
				tempX=inputX/(double)(width-1);
				//check if input is valid
				if(tempX>1){
					tempX=1;
				}else if(tempX<0){
					tempX=0;
				}
				//calculate the Y coordinate depending on the ratio
				tempY=(Math.abs(fixedX-tempX)/ratio)*(inputY/(double)(height-1)-fixedY)/Math.abs(inputY/(double)(height-1)-fixedY) + fixedY;
				//if the the Y coordinate becomes smaller or larger than allowed, set it to the closest allowed value and calculate X depending on the ratio
				if(tempY<0){
					tempY=0;
					tempX=(Math.abs(fixedY-tempY)*ratio)*(tempX-fixedX)/Math.abs(tempX-fixedX) + fixedX;
				}else if(tempY>1){
					tempY=1;
					tempX=(Math.abs(fixedY-tempY)*ratio)*(tempX-fixedX)/Math.abs(tempX-fixedX) + fixedX;
				}
			}else{
				//if the width is going to define the size of final box and the height is calculated accordingly
				tempY=inputY/(double)(height-1);
				//check if input is valid
				if(tempY>1){
					tempY=1;
				}else if(tempY<0){
					tempY=0;
				}
				//calculate the X coordinate depending on the ratio
				tempX=Math.abs(fixedY-tempY)*ratio*(inputX/(double)(width-1)-fixedX)/Math.abs(inputX/(double)(width-1)-fixedX) + fixedX;
				//if the the X coordinate becomes smaller or larger than allowed, set it to the closest allowed value and calculate Y depending on the ratio
				if(tempX<0){
					tempX=0;
					tempY=(Math.abs(fixedX-tempX)/ratio)*(tempY-fixedY)/Math.abs(tempY-fixedY) + fixedY;
				}else if(tempX>1){
					tempX=1;
					tempY=(Math.abs(fixedX-tempX)/ratio)*(tempY-fixedY)/Math.abs(tempY-fixedY) + fixedY;
				}
			}
			//set the original values to the newly calculated ones
			return new double[]{tempX,tempY};
		}else{
			//if there is no depandance of the ratio, the input coordinates are set as the new coordinates for the lower right corner
			return new double[]{inputX/(double)(width-1),inputY/(double)(height-1)};
		}
	}

	public void keyPressed(KeyEvent arg0) {
		//pressing the shift-key keeps the ratio of the box from the moment where the key is pressed
		if(arg0.getKeyCode()==KeyEvent.VK_SHIFT){
			if(!keepRatioKey){
				ratio=(double)(X2-X1+1/(double)(height-1))/(Y2-Y1+1/(double)(height-1));
			}
			keepRatioKey=true;

		//pressing the Control-Key ensures that you get a square when you make a new box
		}else if(arg0.getKeyCode()==KeyEvent.VK_CONTROL){
			if(!keepRatio && !keepRatioKey){
				keepSquare=true;
				ratio=1;
			}
		}else{
			super.keyPressed(arg0);
		}
		updateParent();
	}

	public void keyReleased(KeyEvent arg0) {
		if(arg0.getKeyCode()==KeyEvent.VK_SHIFT){
			keepRatioKey=false;
		}

		if(arg0.getKeyCode()==KeyEvent.VK_CONTROL){
			keepSquare=false;
		}
		updateParent();
	}

	public void setKeepDimension(boolean keepDimension) {
		this.keepDimension = keepDimension;
	}

	public void adjustKeepRatio(boolean keepRatio) {
		this.keepRatio = keepRatio;
                //calculate the ratio from the moment the keepRatio option is set
                ratio=(double)(X2-X1+1/(double)(height-1))/(Y2-Y1+1/(double)(height-1));
	}

	public boolean isKeepRatioKey() {
		return keepRatioKey;
	}

	public void setKeepRatioKey(boolean keepRatioKey) {
		this.keepRatioKey = keepRatioKey;
	}

	public boolean isKeepSquare() {
		return keepSquare;
	}

	public void setKeepSquare(boolean keepSquare) {
		this.keepSquare = keepSquare;
	}

	public double getRatio() {
		return ratio;
	}

	public void setRatio(double ratio) {
		this.ratio = ratio;
	}

	public boolean isKeepDimension() {
		return keepDimension;
	}

	public boolean isKeepRatio() {
		return keepRatio;
	}
}